package adc.app.ui.wicket;

import java.io.Serializable;
import java.util.Iterator;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.feedback.ContainerFeedbackMessageFilter;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.IFormSubmitter;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;

import adc.app.spec.IBo;
import adc.app.ui.wicket.security.AuthenticationRequired;
import adc.app.ui.wicket.security.ComponentAction;
import adc.app.ui.wicket.security.Right;
import adc.app.ui.wicket.security.RightRequired;

@AuthenticationRequired
@RightRequired(value=Right.VIEW, forAction=ComponentAction.RENDER)
public abstract class BoEditPanel<T extends IBo> extends Panel {
	private static final long serialVersionUID = 1L;

	private static final String ID_FORM = "form";
	private static final String ID_FEEDBACK = "feedback";
	private static final String ID_BUTTONS_PANE = "buttonsPane";
	private static final String ID_DEF_BUTTONS_PANE = "defaultButtonsPaneFragment";
	private static final String ID_BUTTON = "button";
	private static final String ID_BUTTON_CAPTION = "caption";
	
	public static interface ISaveCallback<T extends IBo> extends Serializable {
		void save(AjaxRequestTarget target, Form<T> form);
	}
	
	public static interface ICancelCallback extends Serializable {
		void cancel(AjaxRequestTarget target);
	}
	
	private boolean filedsAdded = false;

	private ISaveCallback<T> saveCallback;
	private ICancelCallback cancelCallback;
	
	protected boolean onBeforeSaveCallback(AjaxRequestTarget target, Form<T> form) {
		return true;
	}
	
	public BoEditPanel(String id) {
		super(id);
		
		final Form<T> form = new BypassInnerForm<T>(ID_FORM, 
				new CompoundPropertyModel<T>(new PropertyModel<T>(this, "modelObject")));
		add (form);
		
		add(new AlertFeedbackPanel(ID_FEEDBACK, new ContainerFeedbackMessageFilter(form))
			.setOutputMarkupId(true));
	}
	
	protected abstract void addFields(Form<T> form);
	
	public final ISaveCallback<T> getSaveCallback() {
		return saveCallback;
	}

	public final BoEditPanel<T> setSaveCallback(ISaveCallback<T> saveCallback) {
		this.saveCallback = saveCallback;
		return this;
	}

	public final ICancelCallback getCancelCallback() {
		return cancelCallback;
	}

	public final BoEditPanel<T> setCancelCallback(ICancelCallback cancelCallback) {
		this.cancelCallback = cancelCallback;
		return this;
	}

	protected Component createButtonsPane(String buttonsPaneId) {
		return new ButtonsPaneFragment(buttonsPaneId, this);
	}
	
	@SuppressWarnings("unchecked")
	protected void addButtons(RepeatingView buttons, String captionId) {
		buttons.add(new SaveObjectButton<T>(buttons.newChildId(), captionId, 
				(Form<T>) this.get(ID_FORM)));
		buttons.add(new CancelButton(buttons.newChildId(), captionId));
	}
	
	@SuppressWarnings("unchecked")
	public final IModel<T> getModel() {
		return (IModel<T>) this.getDefaultModel();
	}
	
	public final BoEditPanel<T> setModel(IModel<T> model) {
		this.setDefaultModel(model);
		return this;
	}
	
	@SuppressWarnings("unchecked")
	public final T getModelObject() {
		return (T) this.getDefaultModelObject();
	}
	
	public final BoEditPanel<T> setModelObject(T modelObject) {
		this.setDefaultModelObject(modelObject);
		return this;
	}
	
	@Override
	protected void onBeforeRender() {
		@SuppressWarnings("unchecked")
		Form<T> form = (Form<T>) this.get(ID_FORM);
		if (!filedsAdded) {
			addFields(form);
			add(createButtonsPane(ID_BUTTONS_PANE));
			filedsAdded = true;
		}
		super.onBeforeRender();
	}
	
	private static final class ButtonsPaneFragment extends Fragment {

		private static final long serialVersionUID = 1L;

		public ButtonsPaneFragment(String id, BoEditPanel<?> markupProvider) {
			super(id, ID_DEF_BUTTONS_PANE, markupProvider);
			add(new ButtonsRepeationView(ID_BUTTON));
		}
	}
	
	private static class ButtonsRepeationView extends RepeatingView {

		private static final long serialVersionUID = 1L;
		
		private boolean buttonsAdded;
		
		public ButtonsRepeationView(String id) {
			super(id);
		}

		@Override
		protected void onPopulate() {
			if (!buttonsAdded) {
				BoEditPanel<?> dialog = this.findParent(BoEditPanel.class);
				dialog.addButtons(this, ID_BUTTON_CAPTION);
				for (Iterator<? extends Component> compsIter = this.iterator(); compsIter.hasNext();) {
					Component comp = compsIter.next();
					if (!(comp instanceof MarkupContainer)) {
						throw new WicketRuntimeException("Only MarkupContainer are permited in the buttons pane");
					}
					MarkupContainer mc = (MarkupContainer) comp;
					if (mc.get(ID_BUTTON_CAPTION) == null) {
						mc.add(new WebComponent(ID_BUTTON_CAPTION).setVisible(false));
					}
				}
				buttonsAdded = true;
			}
		}
	}
	
	@RightRequired(Right.MODIFY)
	private static final class SaveObjectButton<T extends IBo> 
		extends AjaxSubmitLink {

		private static final long serialVersionUID = 1L;
		
		public SaveObjectButton(String id, String captionId, Form<T> form) {
			super(id, form);
			add(new Label(captionId, new ResourceModel(IMsgs.SAVE)));
		}
	
		@Override
		protected void onError(AjaxRequestTarget target, Form<?> form) {
			getForm().clearInput();
			target.add(this.getForm().findParent(BoEditPanel.class).get(ID_FEEDBACK));
		}
	
		@SuppressWarnings("unchecked")
		@Override
		protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
			BoEditPanel<T> panel = (BoEditPanel<T>) this.getForm()
					.findParent(BoEditPanel.class);
			if (panel.onBeforeSaveCallback(target, (Form<T>) form)) {
				if (panel.saveCallback != null) {
					panel.saveCallback.save(target, (Form<T>) form);
				}
			}
			target.add(panel.get(ID_FEEDBACK));
		}
	
	}
	
	private static final class CancelButton extends AjaxLink<Object> {

		private static final long serialVersionUID = 1L;

		public CancelButton(String id, String captionId) {
			super(id);
			add(new Label(captionId, new ResourceModel(IMsgs.QUIT)));
		}

		@Override
		public void onClick(AjaxRequestTarget target) {
			BoEditPanel<?> panel = this.findParent(BoEditPanel.class);
			if (panel.cancelCallback != null) {
				panel.cancelCallback.cancel(target);
			}
		}
	}
	
	@RightRequired(value=Right.MODIFY, forAction=ComponentAction.ENABLE)
	private static final class BypassInnerForm<T> extends Form<T> {

		private static final long serialVersionUID = 1L;

		public BypassInnerForm(String id, IModel<T> model) {
			super(id, model);
		}

		@Override
		public boolean isEnabled() {
			if (!super.isEnabled()) {
				return false;
			}
			if (this.getRootForm() == this) {
				return true;
			}
			final IFormSubmitter submit = getRootForm().findSubmittingButton();
			final boolean enabled = submit == null || submit.getForm() == this;
			return enabled;
		}
	}
	
}
